{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R2AxpObRncMd"
      },
      "source": [
        "***Copyright 2020 The TensorFlow Authors.***"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "gQ5Kfh1YnkFS"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uc0VwsT5nvQi"
      },
      "source": [
        "# Tensorflow Lattice를 사용한 윤리에 대한 형상 제약 조건"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gqJQZdvfn32j"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/lattice/tutorials/shape_constraints_for_ethics\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/lattice/tutorials/shape_constraints_for_ethics.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행하기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/lattice/tutorials/shape_constraints_for_ethics.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/lattice/tutorials/shape_constraints_for_ethics.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드하기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YFZbuZMAoBny"
      },
      "source": [
        "## 개요\n",
        "\n",
        "이 튜토리얼에서는 TensorFlow Lattice(TFL) 라이브러리를 사용하여 *책임감* 있게 작동하고 *윤리적*이거나 *공정한* 특정 가정을 위반하지 않는 모델을 훈련하는 방법을 보여줍니다. 특히 특정 속성에 대한 *불공정한 불이익*을 피하기 위해 단조성 제약 조건을 사용하는 데 초점을 맞출 것입니다. 이 튜토리얼에는 Serena Wang 및 Maya Gupta이 [AISTATS 2020](https://www.aistats.org/)에 게재한 [*Deontological Ethics By Monotonicity Shape Constraints(단조성 형상 제약 조건에 의한 의무론적 윤리)*](https://arxiv.org/abs/2001.11990) 논문의 실험 데모가 포함되어 있습니다.\n",
        "\n",
        "공개된 데이터세트에 TFL 사전 구성 estimator를 사용할 것이지만 이 튜토리얼의 모든 내용은 TFL Keras 레이어로 구성된 모델로도 수행할 수 있습니다.\n",
        "\n",
        "계속하기 전에 필요한 모든 패키지가 런타임에 설치되어 있는지 확인하세요(아래 코드 셀에서 가져옴)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o4L76T-NpgCS"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6FvmHcqbpkL7"
      },
      "source": [
        "TF Lattice 패키지 설치하기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f91yvUt_peYs"
      },
      "outputs": [],
      "source": [
        "#@test {\"skip\": true}\n",
        "!pip install tensorflow-lattice seaborn"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6TDoQsvSpmfx"
      },
      "source": [
        "필수 패키지 가져오기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KGt0pm0b1O5X"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "\n",
        "import logging\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import os\n",
        "import pandas as pd\n",
        "import seaborn as sns\n",
        "from sklearn.model_selection import train_test_split\n",
        "import sys\n",
        "import tensorflow_lattice as tfl\n",
        "logging.disable(sys.maxsize)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DFN6GOcBAqzv"
      },
      "source": [
        "이 튜토리얼에서 사용되는 기본값:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9uqMM2joAnoW"
      },
      "outputs": [],
      "source": [
        "# List of learning rate hyperparameters to try.\n",
        "# For a longer list of reasonable hyperparameters, try [0.001, 0.01, 0.1].\n",
        "LEARNING_RATES = [0.01]\n",
        "# Default number of training epochs and batch sizes.\n",
        "NUM_EPOCHS = 1000\n",
        "BATCH_SIZE = 1000\n",
        "# Directory containing dataset files.\n",
        "DATA_DIR = 'https://raw.githubusercontent.com/serenalwang/shape_constraints_for_ethics/master'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OZJQfJvY3ibC"
      },
      "source": [
        "# 사례 연구 #1: 로스쿨 입학\n",
        "\n",
        "이 튜토리얼의 첫 번째 부분에서는 로스쿨 입학 위원회(LSAC)의 로스쿨 입학 데이터세트를 사용한 사례 연구를 살펴봅니다. 학생의 LSAT 점수와 학부 GPA의 두 가지 특성을 사용하여 학생이 기준점을 통과할지 여부를 예측하도록 분류자를 훈련할 것입니다.\n",
        "\n",
        "분류자의 점수가 로스쿨 입학 또는 장학금 판단 요소로 사용되었다고 가정합니다. 성과 기반 사회 규범에 따르면 GPA와 LSAT 점수가 높은 학생이 분류자로부터 더 높은 점수를 받아야 합니다. 그러나 모델이 이러한 직관적인 규범을 위반하기 쉽고 때로는 더 높은 GPA 또는 LSAT 점수를 받은 학생들에게 불이익을 주는 것을 관찰하게 됩니다.\n",
        "\n",
        "이 *불공정한 불이익* 문제를 해결하기 위해 모델이 더 높은 GPA 또는 더 높은 LSAT 점수에 불이익을 주지 않도록 단조성 제약 조건을 적용할 수 있습니다. 이 튜토리얼에서는 TFL을 사용하여 이러한 단조성 제약 조건을 적용하는 방법을 보여줍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vJES8lYT1fHN"
      },
      "source": [
        "## 로스쿨 데이터 로드하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Cl89ZOsQ14An"
      },
      "outputs": [],
      "source": [
        "# Load data file.\n",
        "law_file_name = 'lsac.csv'\n",
        "law_file_path = os.path.join(DATA_DIR, law_file_name)\n",
        "raw_law_df = pd.read_csv(law_file_path, delimiter=',')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RCpTYCNjqOsC"
      },
      "source": [
        "데이터세트 전처리하기:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jdY5rtLs4xQK"
      },
      "outputs": [],
      "source": [
        "# Define label column name.\n",
        "LAW_LABEL = 'pass_bar'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1t1Hd8gu6Uat"
      },
      "outputs": [],
      "source": [
        "def preprocess_law_data(input_df):\n",
        "  # Drop rows with where the label or features of interest are missing.\n",
        "  output_df = input_df[~input_df[LAW_LABEL].isna() & ~input_df['ugpa'].isna() &\n",
        "                       (input_df['ugpa'] > 0) & ~input_df['lsat'].isna()]\n",
        "  return output_df\n",
        "\n",
        "\n",
        "law_df = preprocess_law_data(raw_law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YhvSKr9SCrHP"
      },
      "source": [
        "### 데이터를 훈련/검증/테스트 세트로 분할하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gQKkIGD-CvGD"
      },
      "outputs": [],
      "source": [
        "def split_dataset(input_df, random_state=888):\n",
        "  \"\"\"Splits an input dataset into train, val, and test sets.\"\"\"\n",
        "  train_df, test_val_df = train_test_split(\n",
        "      input_df, test_size=0.3, random_state=random_state)\n",
        "  val_df, test_df = train_test_split(\n",
        "      test_val_df, test_size=0.66, random_state=random_state)\n",
        "  return train_df, val_df, test_df\n",
        "\n",
        "\n",
        "law_train_df, law_val_df, law_test_df = split_dataset(law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zObwzY7f3aLy"
      },
      "source": [
        "### 데이터 분포 시각화하기\n",
        "\n",
        "먼저 데이터 분포를 시각화합니다. 기준점을 통과한 모든 학생들과 통과하지 못한 모든 학생들에 대한 GPA 및 LSAT 점수를 플롯할 것입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dRAZB5cLORUG"
      },
      "outputs": [],
      "source": [
        "def plot_dataset_contour(input_df, title):\n",
        "  plt.rcParams['font.family'] = ['serif']\n",
        "  g = sns.jointplot(\n",
        "      x='ugpa',\n",
        "      y='lsat',\n",
        "      data=input_df,\n",
        "      kind='kde',\n",
        "      xlim=[1.4, 4],\n",
        "      ylim=[0, 50])\n",
        "  g.plot_joint(plt.scatter, c='b', s=10, linewidth=1, marker='+')\n",
        "  g.ax_joint.collections[0].set_alpha(0)\n",
        "  g.set_axis_labels('Undergraduate GPA', 'LSAT score', fontsize=14)\n",
        "  g.fig.suptitle(title, fontsize=14)\n",
        "  # Adust plot so that the title fits.\n",
        "  plt.subplots_adjust(top=0.9)\n",
        "  plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "feovlsWPQhVG"
      },
      "outputs": [],
      "source": [
        "law_df_pos = law_df[law_df[LAW_LABEL] == 1]\n",
        "plot_dataset_contour(\n",
        "    law_df_pos, title='Distribution of students that passed the bar')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ct-2tEedU0aO"
      },
      "outputs": [],
      "source": [
        "law_df_neg = law_df[law_df[LAW_LABEL] == 0]\n",
        "plot_dataset_contour(\n",
        "    law_df_neg, title='Distribution of students that failed the bar')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6grrFEMPfPjk"
      },
      "source": [
        "## 기준점 시험 통과를 예측하도록 보정된 선형 모델 훈련하기\n",
        "\n",
        "다음으로, TFL에서 *보정된 선형 모델*을 훈련하여 학생이 기준점을 통과할지 여부를 예측합니다. 두 가지 입력 특성은 LSAT 점수와 학부 GPA이며, 훈련 레이블은 학생이 기준점을 통과했는지 여부입니다.\n",
        "\n",
        "먼저 제약 조건 없이 보정된 선형 모델을 훈련합니다. 그런 다음, 단조성 제약 조건을 사용하여 보정된 선형 모델을 훈련하고 모델 출력 및 정확성의 차이를 관찰합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vrUZvP8V736o"
      },
      "source": [
        "### TFL 보정 선형 estimator를 훈련하기 위한 도우미 함수\n",
        "\n",
        "이들 함수는 이 로스쿨 사례 연구와 아래의 대출 연체 사례 연구에 사용됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ollW4xAZ72kz"
      },
      "outputs": [],
      "source": [
        "def train_tfl_estimator(train_df, monotonicity, learning_rate, num_epochs,\n",
        "                        batch_size, get_input_fn,\n",
        "                        get_feature_columns_and_configs):\n",
        "  \"\"\"Trains a TFL calibrated linear estimator.\n",
        "\n",
        "  Args:\n",
        "    train_df: pandas dataframe containing training data.\n",
        "    monotonicity: if 0, then no monotonicity constraints. If 1, then all\n",
        "      features are constrained to be monotonically increasing.\n",
        "    learning_rate: learning rate of Adam optimizer for gradient descent.\n",
        "    num_epochs: number of training epochs.\n",
        "    batch_size: batch size for each epoch. None means the batch size is the full\n",
        "      dataset size.\n",
        "    get_input_fn: function that returns the input_fn for a TF estimator.\n",
        "    get_feature_columns_and_configs: function that returns TFL feature columns\n",
        "      and configs.\n",
        "\n",
        "  Returns:\n",
        "    estimator: a trained TFL calibrated linear estimator.\n",
        "\n",
        "  \"\"\"\n",
        "  feature_columns, feature_configs = get_feature_columns_and_configs(\n",
        "      monotonicity)\n",
        "\n",
        "  model_config = tfl.configs.CalibratedLinearConfig(\n",
        "      feature_configs=feature_configs, use_bias=False)\n",
        "\n",
        "  estimator = tfl.estimators.CannedClassifier(\n",
        "      feature_columns=feature_columns,\n",
        "      model_config=model_config,\n",
        "      feature_analysis_input_fn=get_input_fn(input_df=train_df, num_epochs=1),\n",
        "      optimizer=tf.keras.optimizers.Adam(learning_rate))\n",
        "\n",
        "  estimator.train(\n",
        "      input_fn=get_input_fn(\n",
        "          input_df=train_df, num_epochs=num_epochs, batch_size=batch_size))\n",
        "  return estimator\n",
        "\n",
        "\n",
        "def optimize_learning_rates(\n",
        "    train_df,\n",
        "    val_df,\n",
        "    test_df,\n",
        "    monotonicity,\n",
        "    learning_rates,\n",
        "    num_epochs,\n",
        "    batch_size,\n",
        "    get_input_fn,\n",
        "    get_feature_columns_and_configs,\n",
        "):\n",
        "  \"\"\"Optimizes learning rates for TFL estimators.\n",
        "\n",
        "  Args:\n",
        "    train_df: pandas dataframe containing training data.\n",
        "    val_df: pandas dataframe containing validation data.\n",
        "    test_df: pandas dataframe containing test data.\n",
        "    monotonicity: if 0, then no monotonicity constraints. If 1, then all\n",
        "      features are constrained to be monotonically increasing.\n",
        "    learning_rates: list of learning rates to try.\n",
        "    num_epochs: number of training epochs.\n",
        "    batch_size: batch size for each epoch. None means the batch size is the full\n",
        "      dataset size.\n",
        "    get_input_fn: function that returns the input_fn for a TF estimator.\n",
        "    get_feature_columns_and_configs: function that returns TFL feature columns\n",
        "      and configs.\n",
        "\n",
        "  Returns:\n",
        "    A single TFL estimator that achieved the best validation accuracy.\n",
        "  \"\"\"\n",
        "  estimators = []\n",
        "  train_accuracies = []\n",
        "  val_accuracies = []\n",
        "  test_accuracies = []\n",
        "  for lr in learning_rates:\n",
        "    estimator = train_tfl_estimator(\n",
        "        train_df=train_df,\n",
        "        monotonicity=monotonicity,\n",
        "        learning_rate=lr,\n",
        "        num_epochs=num_epochs,\n",
        "        batch_size=batch_size,\n",
        "        get_input_fn=get_input_fn,\n",
        "        get_feature_columns_and_configs=get_feature_columns_and_configs)\n",
        "    estimators.append(estimator)\n",
        "    train_acc = estimator.evaluate(\n",
        "        input_fn=get_input_fn(train_df, num_epochs=1))['accuracy']\n",
        "    val_acc = estimator.evaluate(\n",
        "        input_fn=get_input_fn(val_df, num_epochs=1))['accuracy']\n",
        "    test_acc = estimator.evaluate(\n",
        "        input_fn=get_input_fn(test_df, num_epochs=1))['accuracy']\n",
        "    print('accuracies for learning rate %f: train: %f, val: %f, test: %f' %\n",
        "          (lr, train_acc, val_acc, test_acc))\n",
        "    train_accuracies.append(train_acc)\n",
        "    val_accuracies.append(val_acc)\n",
        "    test_accuracies.append(test_acc)\n",
        "  max_index = val_accuracies.index(max(val_accuracies))\n",
        "  return estimators[max_index]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jeEfKSA7_aOg"
      },
      "source": [
        "### 로스쿨 데이터세트 특성을 구성하기 위한 도우미 함수\n",
        "\n",
        "이들 도우미 함수는 로스쿨 사례 연구에만 해당됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "B6NU6EEKIYMJ"
      },
      "outputs": [],
      "source": [
        "def get_input_fn_law(input_df, num_epochs, batch_size=None):\n",
        "  \"\"\"Gets TF input_fn for law school models.\"\"\"\n",
        "  return tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "      x=input_df[['ugpa', 'lsat']],\n",
        "      y=input_df['pass_bar'],\n",
        "      num_epochs=num_epochs,\n",
        "      batch_size=batch_size or len(input_df),\n",
        "      shuffle=False)\n",
        "\n",
        "\n",
        "def get_feature_columns_and_configs_law(monotonicity):\n",
        "  \"\"\"Gets TFL feature configs for law school models.\"\"\"\n",
        "  feature_columns = [\n",
        "      tf.feature_column.numeric_column('ugpa'),\n",
        "      tf.feature_column.numeric_column('lsat'),\n",
        "  ]\n",
        "  feature_configs = [\n",
        "      tfl.configs.FeatureConfig(\n",
        "          name='ugpa',\n",
        "          lattice_size=2,\n",
        "          pwl_calibration_num_keypoints=20,\n",
        "          monotonicity=monotonicity,\n",
        "          pwl_calibration_always_monotonic=False),\n",
        "      tfl.configs.FeatureConfig(\n",
        "          name='lsat',\n",
        "          lattice_size=2,\n",
        "          pwl_calibration_num_keypoints=20,\n",
        "          monotonicity=monotonicity,\n",
        "          pwl_calibration_always_monotonic=False),\n",
        "  ]\n",
        "  return feature_columns, feature_configs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HSfAwgiO_6YA"
      },
      "source": [
        "### 훈련된 모델의 출력 시각화를 위한 도우미 함수"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HESNIC5H-1dz"
      },
      "outputs": [],
      "source": [
        "def get_predicted_probabilities(estimator, input_df, get_input_fn):\n",
        "  predictions = estimator.predict(\n",
        "      input_fn=get_input_fn(input_df=input_df, num_epochs=1))\n",
        "  return [prediction['probabilities'][1] for prediction in predictions]\n",
        "\n",
        "\n",
        "def plot_model_contour(estimator, input_df, num_keypoints=20):\n",
        "  x = np.linspace(min(input_df['ugpa']), max(input_df['ugpa']), num_keypoints)\n",
        "  y = np.linspace(min(input_df['lsat']), max(input_df['lsat']), num_keypoints)\n",
        "\n",
        "  x_grid, y_grid = np.meshgrid(x, y)\n",
        "\n",
        "  positions = np.vstack([x_grid.ravel(), y_grid.ravel()])\n",
        "  plot_df = pd.DataFrame(positions.T, columns=['ugpa', 'lsat'])\n",
        "  plot_df[LAW_LABEL] = np.ones(len(plot_df))\n",
        "  predictions = get_predicted_probabilities(\n",
        "      estimator=estimator, input_df=plot_df, get_input_fn=get_input_fn_law)\n",
        "  grid_predictions = np.reshape(predictions, x_grid.shape)\n",
        "\n",
        "  plt.rcParams['font.family'] = ['serif']\n",
        "  plt.contour(\n",
        "      x_grid,\n",
        "      y_grid,\n",
        "      grid_predictions,\n",
        "      colors=('k',),\n",
        "      levels=np.linspace(0, 1, 11))\n",
        "  plt.contourf(\n",
        "      x_grid,\n",
        "      y_grid,\n",
        "      grid_predictions,\n",
        "      cmap=plt.cm.bone,\n",
        "      levels=np.linspace(0, 1, 11))  # levels=np.linspace(0,1,8));\n",
        "  plt.xticks(fontsize=20)\n",
        "  plt.yticks(fontsize=20)\n",
        "\n",
        "  cbar = plt.colorbar()\n",
        "  cbar.ax.set_ylabel('Model score', fontsize=20)\n",
        "  cbar.ax.tick_params(labelsize=20)\n",
        "\n",
        "  plt.xlabel('Undergraduate GPA', fontsize=20)\n",
        "  plt.ylabel('LSAT score', fontsize=20)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fAMSCaRHIn1w"
      },
      "source": [
        "## 제약이 없는(단조가 아닌) 보정 선형 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Iff8omH3Ij_x"
      },
      "outputs": [],
      "source": [
        "nomon_linear_estimator = optimize_learning_rates(\n",
        "    train_df=law_train_df,\n",
        "    val_df=law_val_df,\n",
        "    test_df=law_test_df,\n",
        "    monotonicity=0,\n",
        "    learning_rates=LEARNING_RATES,\n",
        "    batch_size=BATCH_SIZE,\n",
        "    num_epochs=NUM_EPOCHS,\n",
        "    get_input_fn=get_input_fn_law,\n",
        "    get_feature_columns_and_configs=get_feature_columns_and_configs_law)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Gxfv8hXMh4_E"
      },
      "outputs": [],
      "source": [
        "plot_model_contour(nomon_linear_estimator, input_df=law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eKVkjHg_LaWb"
      },
      "source": [
        "## 단조성 보정 선형 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "C_MUEvGNp6g2"
      },
      "outputs": [],
      "source": [
        "mon_linear_estimator = optimize_learning_rates(\n",
        "    train_df=law_train_df,\n",
        "    val_df=law_val_df,\n",
        "    test_df=law_test_df,\n",
        "    monotonicity=1,\n",
        "    learning_rates=LEARNING_RATES,\n",
        "    batch_size=BATCH_SIZE,\n",
        "    num_epochs=NUM_EPOCHS,\n",
        "    get_input_fn=get_input_fn_law,\n",
        "    get_feature_columns_and_configs=get_feature_columns_and_configs_law)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ABdhYOUVCXzD"
      },
      "outputs": [],
      "source": [
        "plot_model_contour(mon_linear_estimator, input_df=law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fsI14lrFxRha"
      },
      "source": [
        "## 다른 제약이 없는 모델 훈련하기\n",
        "\n",
        "TFL 보정 선형 모델이 정확성을 크게 희생하지 않고도 LSAT 점수와 GPA 모두에서 단조롭도록 훈련될 수 있음을 입증했습니다.\n",
        "\n",
        "그렇다면 보정 선형 모델이 심층 신경망(DNN) 또는 그래디언트 부스트 트리(GBT)와 같은 다른 형태의 모델과 어떻게 비교될까요? DNN과 GBT가 합리적으로 공정한 출력을 제공하는 것으로 보입니까? 이 질문의 해답을 얻기 위해 이제 제약이 없는 DNN 및 GBT를 훈련할 것입니다. 실제로, DNN과 GBT 모두 LSAT 점수와 학부 GPA에서 단조성을 쉽게 위반한다는 사실을 관찰하게 될 것입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uo1ruWXcvUqb"
      },
      "source": [
        "### 제약이 없는 심층 신경망(DNN) 모델 훈련하기\n",
        "\n",
        "앞서 높은 검증 정확성을 얻기 위해 아키텍처를 최적화했습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3pplraob0Od-"
      },
      "outputs": [],
      "source": [
        "feature_names = ['ugpa', 'lsat']\n",
        "\n",
        "dnn_estimator = tf.estimator.DNNClassifier(\n",
        "    feature_columns=[\n",
        "        tf.feature_column.numeric_column(feature) for feature in feature_names\n",
        "    ],\n",
        "    hidden_units=[100, 100],\n",
        "    optimizer=tf.keras.optimizers.Adam(learning_rate=0.008),\n",
        "    activation_fn=tf.nn.relu)\n",
        "\n",
        "dnn_estimator.train(\n",
        "    input_fn=get_input_fn_law(\n",
        "        law_train_df, batch_size=BATCH_SIZE, num_epochs=NUM_EPOCHS))\n",
        "dnn_train_acc = dnn_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_train_df, num_epochs=1))['accuracy']\n",
        "dnn_val_acc = dnn_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_val_df, num_epochs=1))['accuracy']\n",
        "dnn_test_acc = dnn_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_test_df, num_epochs=1))['accuracy']\n",
        "print('accuracies for DNN: train: %f, val: %f, test: %f' %\n",
        "      (dnn_train_acc, dnn_val_acc, dnn_test_acc))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LwPQqLt-E7R4"
      },
      "outputs": [],
      "source": [
        "plot_model_contour(dnn_estimator, input_df=law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OOAKK0_3vWir"
      },
      "source": [
        "### 제약이 없는 그래디언트 부스트 트리(GBT) 모델 훈련하기\n",
        "\n",
        "앞서 높은 검증 정확성을 얻기 위해 트리 구조를 최적화했습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mFaI9hB-rgoL"
      },
      "outputs": [],
      "source": [
        "tree_estimator = tf.estimator.BoostedTreesClassifier(\n",
        "    feature_columns=[\n",
        "        tf.feature_column.numeric_column(feature) for feature in feature_names\n",
        "    ],\n",
        "    n_batches_per_layer=2,\n",
        "    n_trees=20,\n",
        "    max_depth=4)\n",
        "\n",
        "tree_estimator.train(\n",
        "    input_fn=get_input_fn_law(\n",
        "        law_train_df, num_epochs=NUM_EPOCHS, batch_size=BATCH_SIZE))\n",
        "tree_train_acc = tree_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_train_df, num_epochs=1))['accuracy']\n",
        "tree_val_acc = tree_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_val_df, num_epochs=1))['accuracy']\n",
        "tree_test_acc = tree_estimator.evaluate(\n",
        "    input_fn=get_input_fn_law(law_test_df, num_epochs=1))['accuracy']\n",
        "print('accuracies for GBT: train: %f, val: %f, test: %f' %\n",
        "      (tree_train_acc, tree_val_acc, tree_test_acc))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AZFyfQT1E_nR"
      },
      "outputs": [],
      "source": [
        "plot_model_contour(tree_estimator, input_df=law_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uX2qiMlrY8aO"
      },
      "source": [
        "# 사례 연구 #2: 대출 연체\n",
        "\n",
        "이 튜토리얼에서 고려할 두 번째 사례 연구는 개인의 대출 연체 확률을 예측하는 것입니다. UCI 리포지토리의 Default of Credit Card Clients 데이터세트를 사용합니다. 이 데이터는 30,000명의 대만 신용카드 사용자로부터 수집되었으며 사용자가 일정 기간 내에 결제를 불이행했는지 여부를 나타내는 바이너리 레이블을 포함하고 있습니다. 특성에는 결혼 여부, 성별, 학력, 사용자가 2005년 4월부터 9월까지 월별로 기존 청구액을 연체한 기간이 포함됩니다.\n",
        "\n",
        "첫 번째 사례 연구에서와 마찬가지로, *불공정한 불이익*을 피하기 위해 단조성 제약 조건을 사용하는 방법을 다시 설명합니다. 모델을 사용하여 사용자의 신용 점수를 결정하는 경우, 다른 모든 조건이 동일할 때 청구액을 조기에 지불하는 것에 대해 불이익을 받는다면 많은 사람들이 불공정하다고 느낄 수 있습니다. 따라서 모델이 조기 결제에 불이익을 주지 않도록 하는 단조성 제약 조건을 적용합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tz5yduNuFinA"
      },
      "source": [
        "## 대출 연체 데이터 로드하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KuylMNBCILwy"
      },
      "outputs": [],
      "source": [
        "# Load data file.\n",
        "credit_file_name = 'credit_default.csv'\n",
        "credit_file_path = os.path.join(DATA_DIR, credit_file_name)\n",
        "credit_df = pd.read_csv(credit_file_path, delimiter=',')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Hv_GQcEHIf9v"
      },
      "outputs": [],
      "source": [
        "# Define label column name.\n",
        "CREDIT_LABEL = 'default'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "13oZWY0YIoy3"
      },
      "source": [
        "### 데이터를 훈련/검증/테스트 세트로 분할하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dty5tXJqIscz"
      },
      "outputs": [],
      "source": [
        "credit_train_df, credit_val_df, credit_test_df = split_dataset(credit_df)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_kAciWXHKGV7"
      },
      "source": [
        "### 데이터 분포 시각화하기\n",
        "\n",
        "먼저 데이터 분포를 시각화합니다. 결혼 여부와 상환 상태가 서로 다른 사람들에 대해 관찰된 연체율의 평균 및 표준 오차를 플롯할 것입니다. 상환 상태는 대출 상환 기간(2005년 4월 현재)에 연체된 개월 수를 나타냅니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8CxacQxnkHWE"
      },
      "outputs": [],
      "source": [
        "def get_agg_data(df, x_col, y_col, bins=11):\n",
        "  xbins = pd.cut(df[x_col], bins=bins)\n",
        "  data = df[[x_col, y_col]].groupby(xbins).agg(['mean', 'sem'])\n",
        "  return data\n",
        "\n",
        "\n",
        "def plot_2d_means_credit(input_df, x_col, y_col, x_label, y_label):\n",
        "  plt.rcParams['font.family'] = ['serif']\n",
        "  _, ax = plt.subplots(nrows=1, ncols=1)\n",
        "  plt.setp(ax.spines.values(), color='black', linewidth=1)\n",
        "  ax.tick_params(\n",
        "      direction='in', length=6, width=1, top=False, right=False, labelsize=18)\n",
        "  df_single = get_agg_data(input_df[input_df['MARRIAGE'] == 1], x_col, y_col)\n",
        "  df_married = get_agg_data(input_df[input_df['MARRIAGE'] == 2], x_col, y_col)\n",
        "  ax.errorbar(\n",
        "      df_single[(x_col, 'mean')],\n",
        "      df_single[(y_col, 'mean')],\n",
        "      xerr=df_single[(x_col, 'sem')],\n",
        "      yerr=df_single[(y_col, 'sem')],\n",
        "      color='orange',\n",
        "      marker='s',\n",
        "      capsize=3,\n",
        "      capthick=1,\n",
        "      label='Single',\n",
        "      markersize=10,\n",
        "      linestyle='')\n",
        "  ax.errorbar(\n",
        "      df_married[(x_col, 'mean')],\n",
        "      df_married[(y_col, 'mean')],\n",
        "      xerr=df_married[(x_col, 'sem')],\n",
        "      yerr=df_married[(y_col, 'sem')],\n",
        "      color='b',\n",
        "      marker='^',\n",
        "      capsize=3,\n",
        "      capthick=1,\n",
        "      label='Married',\n",
        "      markersize=10,\n",
        "      linestyle='')\n",
        "  leg = ax.legend(loc='upper left', fontsize=18, frameon=True, numpoints=1)\n",
        "  ax.set_xlabel(x_label, fontsize=18)\n",
        "  ax.set_ylabel(y_label, fontsize=18)\n",
        "  ax.set_ylim(0, 1.1)\n",
        "  ax.set_xlim(-2, 8.5)\n",
        "  ax.patch.set_facecolor('white')\n",
        "  leg.get_frame().set_edgecolor('black')\n",
        "  leg.get_frame().set_facecolor('white')\n",
        "  leg.get_frame().set_linewidth(1)\n",
        "  plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VHXyYbyekKLT"
      },
      "outputs": [],
      "source": [
        "plot_2d_means_credit(credit_train_df, 'PAY_0', 'default',\n",
        "                     'Repayment Status (April)', 'Observed default rate')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4hnZBigB7kzY"
      },
      "source": [
        "## 대출 연체율을 예측하도록 보정 선형 모델 훈련하기\n",
        "\n",
        "다음으로, TFL에서 *보정 선형 모델*을 훈련하여 개인이 대출을 불이행할지 여부를 예측합니다. 두 가지 입력 특성은 결혼 여부와 4월에 대출금을 연체한 개월 수(상환 상태)입니다. 훈련 레이블은 대출을 연체했는지 여부입니다.\n",
        "\n",
        "먼저 제약 조건 없이 보정된 선형 모델을 훈련합니다. 그런 다음, 단조성 제약 조건을 사용하여 보정된 선형 모델을 훈련하고 모델 출력 및 정확성의 차이를 관찰합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UEcHW1u3Jk_2"
      },
      "source": [
        "### 대출 연체 데이터세트 특성을 구성하기 위한 도우미 함수\n",
        "\n",
        "이들 도우미 함수는 대출 연체 사례 연구에만 해당합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "QBa-hczLi7DM"
      },
      "outputs": [],
      "source": [
        "def get_input_fn_credit(input_df, num_epochs, batch_size=None):\n",
        "  \"\"\"Gets TF input_fn for credit default models.\"\"\"\n",
        "  return tf.compat.v1.estimator.inputs.pandas_input_fn(\n",
        "      x=input_df[['MARRIAGE', 'PAY_0']],\n",
        "      y=input_df['default'],\n",
        "      num_epochs=num_epochs,\n",
        "      batch_size=batch_size or len(input_df),\n",
        "      shuffle=False)\n",
        "\n",
        "\n",
        "def get_feature_columns_and_configs_credit(monotonicity):\n",
        "  \"\"\"Gets TFL feature configs for credit default models.\"\"\"\n",
        "  feature_columns = [\n",
        "      tf.feature_column.numeric_column('MARRIAGE'),\n",
        "      tf.feature_column.numeric_column('PAY_0'),\n",
        "  ]\n",
        "  feature_configs = [\n",
        "      tfl.configs.FeatureConfig(\n",
        "          name='MARRIAGE',\n",
        "          lattice_size=2,\n",
        "          pwl_calibration_num_keypoints=3,\n",
        "          monotonicity=monotonicity,\n",
        "          pwl_calibration_always_monotonic=False),\n",
        "      tfl.configs.FeatureConfig(\n",
        "          name='PAY_0',\n",
        "          lattice_size=2,\n",
        "          pwl_calibration_num_keypoints=10,\n",
        "          monotonicity=monotonicity,\n",
        "          pwl_calibration_always_monotonic=False),\n",
        "  ]\n",
        "  return feature_columns, feature_configs"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iwxnlRrQPdTg"
      },
      "source": [
        "### 훈련된 모델의 출력 시각화를 위한 도우미 함수"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zVGxEfbhPZ5H"
      },
      "outputs": [],
      "source": [
        "def plot_predictions_credit(input_df,\n",
        "                            estimator,\n",
        "                            x_col,\n",
        "                            x_label='Repayment Status (April)',\n",
        "                            y_label='Predicted default probability'):\n",
        "  predictions = get_predicted_probabilities(\n",
        "      estimator=estimator, input_df=input_df, get_input_fn=get_input_fn_credit)\n",
        "  new_df = input_df.copy()\n",
        "  new_df.loc[:, 'predictions'] = predictions\n",
        "  plot_2d_means_credit(new_df, x_col, 'predictions', x_label, y_label)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UMIpywE1P07H"
      },
      "source": [
        "## 제약이 없는(단조가 아닌) 보정 선형 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "FfXUKns5cPEw"
      },
      "outputs": [],
      "source": [
        "nomon_linear_estimator = optimize_learning_rates(\n",
        "    train_df=credit_train_df,\n",
        "    val_df=credit_val_df,\n",
        "    test_df=credit_test_df,\n",
        "    monotonicity=0,\n",
        "    learning_rates=LEARNING_RATES,\n",
        "    batch_size=BATCH_SIZE,\n",
        "    num_epochs=NUM_EPOCHS,\n",
        "    get_input_fn=get_input_fn_credit,\n",
        "    get_feature_columns_and_configs=get_feature_columns_and_configs_credit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5zQ_jm75kRX6"
      },
      "outputs": [],
      "source": [
        "plot_predictions_credit(credit_train_df, nomon_linear_estimator, 'PAY_0')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0aokp7qLQBIr"
      },
      "source": [
        "## 단조 보정 선형 모델 훈련하기"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wWCG7YrLUZDH"
      },
      "outputs": [],
      "source": [
        "mon_linear_estimator = optimize_learning_rates(\n",
        "    train_df=credit_train_df,\n",
        "    val_df=credit_val_df,\n",
        "    test_df=credit_test_df,\n",
        "    monotonicity=1,\n",
        "    learning_rates=LEARNING_RATES,\n",
        "    batch_size=BATCH_SIZE,\n",
        "    num_epochs=NUM_EPOCHS,\n",
        "    get_input_fn=get_input_fn_credit,\n",
        "    get_feature_columns_and_configs=get_feature_columns_and_configs_credit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JCQ2eMdndFhR"
      },
      "outputs": [],
      "source": [
        "plot_predictions_credit(credit_train_df, mon_linear_estimator, 'PAY_0')"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "shape_constraints_for_ethics.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
